
import UIKit
import ImageIO


public extension UIImage {
    
    public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
        let rect = CGRect(origin: .zero, size: size)
        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
        color.setFill()
        UIRectFill(rect)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        guard let cgImage = image?.cgImage else { return nil }
        self.init(cgImage: cgImage)
    }
    
    /// To make screenshot
    ///
    /// - returns: image of screenshot
    public class var screenShot: UIImage {
        
        let window = UIApplication.shared.keyWindow
        
        if UIScreen.main.responds(to: #selector(NSDecimalNumberBehaviors.scale)) {
            
            UIGraphicsBeginImageContextWithOptions(window!.bounds.size, false, UIScreen.main.scale)
            
        } else {
            
            UIGraphicsBeginImageContext(window!.bounds.size)
            
        }
        
        window?.layer.render(in: UIGraphicsGetCurrentContext()!)
        
        let image = UIGraphicsGetImageFromCurrentImageContext()
        
        UIGraphicsEndImageContext()
        
        return image!
    }
    
    /// To make the image always up
    ///
    /// - returns: image of corrected
    func transformImageTocorrectDirection() -> UIImage {
        
        UIGraphicsBeginImageContext(self.size)
        self.draw(at: .zero)
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage ?? self
        
    }
    
    /// To do compressionQuality for image and return the imageData of compressed
    ///
    /// - Parameters:
    ///   - ratio: ratio for compressionQuality
    ///   - baseSizeByte: only compression quality if the image data byte big than the baseSizeByte
    /// - Returns: compression quality data of the image
    func imageRepresentationWith(ratio: CGFloat, baseSizeByte: Int) -> Data? {
        
        var imageData: Data?
        
        if let data = self.jpegData(compressionQuality: 1.0) {
            imageData = data
            guard data.count > baseSizeByte else {
                return imageData
            }
            imageData = self.jpegData(compressionQuality: ratio)
        } else {
            NSLog("imageRepresentation failed, UIImageJPEGRepresentation return nil data")
        }
        
        return imageData
    }
    
    /// To do compressionQuality for image and return the imageData of compressed
    ///
    /// - Parameter byte: Request data size (e.g. 1MB = 1024 * 1024 byte)
    /// - Returns: image data
    public func imageRepresentationWithByteSize(byte: Int) -> Data? {
        
        guard var imageData = self.jpegData(compressionQuality: 1.0) else {
            return nil
        }
        
        var i: Double = 5
        while imageData.count > byte && i > 0 {
            i -= 1
            imageData = self.jpegData(compressionQuality: CGFloat(0.2*(i)))!
        }
        
        return imageData
    }
    
    /// To do compressionQuality for image and return the imageData of compressed
    ///
    /// - Parameter byte: Request image size (e.g. 1MB = 1024 * 1024 byte)
    /// - Returns: request image
    func imageRepresentationJpegWithByteSize(byte: Int) -> UIImage? {
        
        guard let imageData = imageRepresentationWithByteSize(byte: byte) else {
            return nil
        }
        
        return UIImage(data: imageData)
    }
    
    /// Resize image with the biggest value the sent, the value should use for Width/Height of the result Image(e.g. sizeValue = 300, means the Width/Height biggest value is 300, and the Height/Width value should small/equal to the other one). Please be attention: the image storage would be changed after the resize, but not clean of the changing value
    ///
    /// - Parameter sizeValue: the request biggest value of the width/height of the result image
    /// - Returns: request image
    func imageResizeWithConstantSize(sizeValue: CGFloat) -> UIImage {
        
        if self.size.width > sizeValue || self.size.height > sizeValue {
            
            var btWidth: CGFloat = 0.0
            var btHeight: CGFloat = 0.0
            
            if self.size.width < self.size.height {
                btHeight = sizeValue
                btWidth = self.size.width * (sizeValue / self.size.height)
            } else {
                btWidth = sizeValue
                btHeight = self.size.height * (sizeValue / self.size.width)
            }
            
            let targetSize = CGSize(width: btWidth, height: btHeight)
            return UIImage.resizeImage(image: self, targetSize: targetSize)!
        }
        
        return self
    }
    
    /// To resize the image as the targetSize, the storage of the image should be changed but not clean of the changing value
    ///
    /// - Parameters:
    ///   - image: the source image which should be resized
    ///   - targetSize: the size requested of the result image
    /// - Returns: request image
    public class func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage? {
        let size = image.size
        
        let widthRatio  = targetSize.width  / size.width
        let heightRatio = targetSize.height / size.height
        
        // Figure out what our orientation is, and use that to form the rectangle
        var newSize: CGSize
        if(widthRatio > heightRatio) {
            newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
        } else {
            newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
        }
        
        // This is the rect that we've calculated out and this is what is actually used below
        let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
        
        // Actually do the resizing to the rect using the ImageContext stuff
        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
        image.draw(in: rect)
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return newImage
    }
    
}

public extension UIImage {
    
    public class func gifImageWithURL(gifUrl:String) -> UIImage? {
        guard let bundleURL:URL = URL(string: gifUrl)
            else {
                print("image named \"\(gifUrl)\" doesn't exist")
                return nil
        }
        guard let imageData = NSData(contentsOf: bundleURL) else {
            print("image named \"\(gifUrl)\" into NSData")
            return nil
        }
        
        if bundleURL.pathExtension == "gif" {
            return gifImageWithData(data: imageData)
        } else {
            return UIImage(data: imageData as Data)
        }
    }
    
    public class func gifImageWithName(name: String) -> UIImage? {
        guard let bundleURL = Bundle.main
            .url(forResource: name, withExtension: "gif") else {
                print("SwiftGif: This image named \"\(name)\" does not exist")
                return nil
        }
        guard let imageData = NSData(contentsOf: bundleURL) else {
            print("SwiftGif: Cannot turn image named \"\(name)\" into NSData")
            return nil
        }
        
        return gifImageWithData(data: imageData)
    }
    
    public class func gifImageWithData(data: NSData) -> UIImage? {
        guard let source = CGImageSourceCreateWithData(data, nil) else {
            print("image doesn't exist")
            return nil
        }
        
        return UIImage.animatedImageWithSource(source: source)
    }
    
    class func delayForImageAtIndex(index: Int, source: CGImageSource!) -> Double {
        var delay = 0.1
        
        let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
        let gifProperties: CFDictionary = unsafeBitCast(
            CFDictionaryGetValue(cfProperties,
                                 Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()),
            to: CFDictionary.self)
        
        var delayObject: AnyObject = unsafeBitCast(
            CFDictionaryGetValue(gifProperties,
                                 Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
            to: AnyObject.self)
        if delayObject.doubleValue == 0 {
            delayObject = unsafeBitCast(
                CFDictionaryGetValue(gifProperties,
                                     Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()),
                to: AnyObject.self)
        }
        
        delay = delayObject as! Double
        
        if delay < 0.1 {
            delay = 0.1
        }
        
        return delay
    }
    
    class func gcdForPair(a: Int?, _ b: Int?) -> Int {
        var a = a
        var b = b
        if b == nil || a == nil {
            if b != nil {
                return b!
            } else if a != nil {
                return a!
            } else {
                return 0
            }
        }
        
        if a! < b! {
            let c = a
            a = b
            b = c
        }
        
        var rest: Int
        while true {
            rest = a! % b!
            
            if rest == 0 {
                return b!
            } else {
                a = b
                b = rest
            }
        }
    }
    
    class func gcdForArray(array: Array<Int>) -> Int {
        if array.isEmpty {
            return 1
        }
        
        var gcd = array[0]
        for val in array {
            gcd = UIImage.gcdForPair(a: val, gcd)
        }
        return gcd
    }
    
    class func animatedImageWithSource(source: CGImageSource) -> UIImage? {
        let count = CGImageSourceGetCount(source)
        var images = [CGImage]()
        var delays = [Int]()
        
        for i in 0..<count {
            if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
                images.append(image)
            }
            
            let delaySeconds = UIImage.delayForImageAtIndex(index: Int(i),
                                                            source: source)
            delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
        }
        
        let duration: Int = {
            var sum = 0
            
            for val: Int in delays {
                sum += val
            }
            
            return sum
        }()
        
        let gcd = gcdForArray(array: delays)
        var frames = [UIImage]()
        
        var frame: UIImage
        var frameCount: Int
        for i in 0..<count {
            frame = UIImage(cgImage: images[Int(i)])
            frameCount = Int(delays[Int(i)] / gcd)
            
            for _ in 0..<frameCount {
                frames.append(frame)
            }
        }
        
        let animation = UIImage.animatedImage(with: frames,
                                              duration: Double(duration) / 1000.0)
        
        return animation
    }
    
}

